<?php
namespace cw;

use Workerman\Worker;
use Workerman\Protocols\Http\Response;

class Route extends Base
{
    protected $route = [];
    protected $dispatcher = null;

    public function __construct()
    {
        global $route;
        $this->route = &$route;
        if (!$this->route) {
            $this->route = require_once \__ROUTE__ . 'routes.php';
        }

        $this->createRoute();
    }


    /**
     * @name: createRoute
     * @msg: 根据路由配置文件,调用 fast-route 库定义路由
     * @param {type}
     * @return {type}
     */
    public function createRoute()
    {
        $route = $this->route;

        if (config('app.app_debug')) {
            $add_route_func = 'FastRoute\simpleDispatcher';
            $option = [];
        } else {
            $add_route_func = 'FastRoute\cachedDispatcher';
            $option = [
                'cacheFile' => __RUNTIME__ . 'cache/route.cache',
                // 'cacheDisabled' => IS_DEBUG_ENABLED,
            ];
        }

        $dispatcher = $add_route_func(function (\FastRoute\RouteCollector $r) use ($route) {
            foreach ($route as $rk => $rv) {
                // print_r($rv);
                // var_dump(rtrim($rv['url_prefix'], '/'));
                $r->addGroup(rtrim($rv['url_prefix'], '/'), function (\FastRoute\RouteCollector $rs) use ($rv) {
                    foreach ($rv['route'] as $key => $val) {
                        // var_dump($val);
                        if (is_string($val[2])) {
                            // var_dump($rv['namespace_prefix'] . $val[2]);
                            $rs->addRoute(strtoupper($val[0]), $val[1], $rv['namespace_prefix'] . $val[2]);
                        } elseif (is_callable($val[2])) {
                            $rs->addRoute(strtoupper($val[0]), $val[1], $val[2]);
                        }
                    }
                });
            }
        }, $option);

        $this->dispatcher = $dispatcher;
    }

    /**
     * @name: dispatch
     * @msg: 路由解析与分发,如果是http协议,则 $data 是 http request 对象,其他协议,则是接收到的数据,该数据应该在协议解析里面解析为数组,且数组的第一层应该有一个名为 uri 的key.建议格式为
     *  [
     *      'uri' => 'http://www.caseworker.com:2345/hello',
     *     'data' => [
     *         ...
     *     ],
     * ]
     * @param object $connection 当前连接对象
     * @param object $request 当前请求对象
     * @return void
     */
    public function dispatch($connection, $data)
    {
        $protocol = strtolower(trim(strrchr($connection->protocol, '\\'), '\\'));
        if ($protocol === 'http') {
            $method = $data->method();
            $uri = $data->uri();
        } else {
            if (!is_array($data)) {
                // throw new RuntimeException('have a bad format for request data');
                // $connection->send(new Response(500, [], 'have a bad format for request data'));
                send($connection, 'have a bad format for request data', 500);
                return ;
            }
            $method = 'get';
            $uri = $data['uri'];
        }

        // Strip query string (?foo=bar) and decode URI
        if (false !== $pos = strpos($uri, '?')) {
            $uri = substr($uri, 0, $pos);
        }

        // $response = new Response();
        $uri = rawurldecode($uri);
        // var_dump($method, $uri);

        $routeInfo = $this->dispatcher->dispatch($method, $uri);
        // var_dump('routeInfo: ', $routeInfo);
        // var_dump('uri: ', $uri);
        // return ;
        switch ($routeInfo[0]) {
            case \FastRoute\Dispatcher::NOT_FOUND:
                $uri = trim($uri, '/');
                $uri = explode('/', $uri);
                // var_dump($uri);

                // defualt route
                if ($uri[0] === '') {
                    $moudle = config('app.default_moudle');
                    $moudle_path = \__APP__ . $protocol . '/controller/' . $moudle;
                    if (!file_exists($moudle_path)) {
                        // throw new \RuntimeException('Moudle ' . $moudle . ' not exists');
                        send($connection, 'Moudle ' . $moudle . ' not exists', 500);
                        return ;
                    }
                    $classname = ucwords(config('app.default_controller'));
                    $class = '\\app\\' . $protocol . '\\controller\\' . $moudle . '\\' . $classname;
                    if (!class_exists($class)) {
                        // throw new \RuntimeException('Class ' . config('app.default_controller') . ' not exists');
                        send($connection, 'Class ' . $classname . ' not exists', 500);
                        return ;
                    }
                    $action = config('app.default_action');
                    if (!\method_exists($class, $action)) {
                        // throw new \RuntimeException('Action ' . $action . ' not exists');
                        send($connection, 'Action ' . $action . ' not exists', 500);
                        return ;
                    }

                    $res = (new $class)->$action($data);
                    send($connection, $res);
                } else {
                    $path = __ROOT__ . trim(\implode($uri, '/'), '/');
                    // var_dump($path);
                    if (\file_exists($path)) {

                        $headers = [
                            'Content-Type' => mime_content_type($path),
                            'Access-Control-Allow-Origin' => '*',
                            'Access-Control-Allow-Headers' => 'token,Origin,X-Requested-With,Content-Type,Accept,rtime',
                            'Access-Control-Allow-Methods' => 'GET,POST,PATCH,PUT,DELETE,OPTIONS',
                        ];
                        $response = new Response(200, $headers);
                        $response->withFile($path);
                        $connection->send($response);
                        return ;
                    } else {
                        // throw new \RuntimeException('The request file not fund', 404);
                        send($connection, 'The request file not fund', 404);
                        return ;
                    }
                }
                break;
            case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
                // $allowedMethods = $routeInfo[1];
                // ... 405 Method Not Allowed
                // $response->withStatus(405);
                // throw new \RuntimeException('The request method not allowed', 405);
                send($connection, 'The request method not allowed', 405, ['Allow:' => $routeInfo[1]]);
                break;
            case \FastRoute\Dispatcher::FOUND:
                $handler = $routeInfo[1];
                $vars = $routeInfo[2];
                // ... call $handler with $vars
                if (is_string($handler)) {
                    $handler_arr = explode('@', $handler);
                    if (count($handler_arr) != 2) {
                        // throw new \RuntimeException('Route ' . $uri . ' config error, Only @ are supported');
                        send($connection, 'Route ' . $uri . ' config error, Only @ are supported', 500);
                        return ;
                    }

                    $class_name = $handler_arr[0];
                    $func = $handler_arr[1];

                    if (! class_exists($class_name)) {
                        // throw new \RuntimeException("Route {$uri} defined '{$class_name}' Class Not Found");
                        send($connection, "Route '{$uri}' defined '{$class_name}' Class Not Found", 500);
                        return ;
                    }

                    if (! method_exists($class_name, $func)) {
                        // throw new \RuntimeException("Route {$uri} defined '{$func}' Method Not Found");
                        send($connection, "Route '{$uri}' defined '{$func}' Method Not Found", 500);
                        return ;
                    }
                    $res = (new $class_name)->$func($data, $vars);
                    // var_dump();
                    send($connection, $res);
                    return ;
                } elseif (\is_callable($handler)) {
                    $res = call_user_func($handler, $data);
                    send($connection, $res, 200);
                    return ;
                } else {
                    send($connection, 'Route defined has a bed format.', 500);
                    return ;
                }
                break;
            default:
                // $response->status(400);
                send($connection, '', 400);
        }
    }
}
